/*
 * Copyright (c) 2011 Obix Labs Limited
 * Redistribution and use in source and binary forms, 
 * with or without modification, are permitted provided 
 * that the following conditions are met:
 * 
 * 		Redistribution of source code must retain the above 
 * 		copyright notice, this list of conditions and the 
 * 		following disclaimer.
 *
 * 		Redistribution in binary form must reproduce the 
 * 		above copyright notice, this list of conditions 
 * 		and the following disclaimer in the documentation 
 * 		and/or other materials provided with the distribution.
 * 
 * 	THIS SOFTWARE IS PROVIDED "AS IS," WITHOUT A WARRANTY OF 
 * 	ANY KIND. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS 
 * 	AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
 * 	FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, 
 * 	ARE HEREBY EXCLUDED. OBIX LABS LIMITED ("Obix Labs") AND ITS 
 * 	LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE 
 * 	AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR 
 * 	ITS DERIVATIVES. IN NO EVENT WILL Obix Labs OR ITS LICENSORS BE 
 * 	LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, 
 * 	INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE 
 * 	DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF 
 * 	LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS 
 * 	SOFTWARE, EVEN IF Obix Labs HAS BEEN ADVISED OF THE POSSIBILITY OF 
 * 	SUCH DAMAGES.
 */

package com.obixlabs.commons.util;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;


/**
 * <p>
 * A thread-safe implementation of the {@link Map} interface which maps single keys to multiple-values.
 * This is achieved by encapsulating all the values for a specific key within 
 * a {@link MultiValueMapEntry} object. 
 * </p>
 * <p>
 * Each {@link MultiValueMapEntry} instance is mapped against its key as given by the 
 * {@link MultiValueMapEntry#key()} method. Thus this implementation only 
 * permits values of type {@link MultiValueMapEntry}, but permits keys of any type. 
 * </p>
 * <p>
 * This implementation does not support duplicate keys, however it does support 
 * <code>null</code> keys and <code>null</code> values.
 * </p> 
 * 
 * @see MultiValueMapEntry
 * @see Map
 */
public class MultiValueMap implements Map<Object,MultiValueMapEntry>, Serializable
{
	private static final long serialVersionUID = 7065107315915368021L;
	
	/**
	 * <p>The backing map which is used for the storage of entries and entry keys.</p>
	 */
	protected Map<Object,MultiValueMapEntry> entries;

	/**
	 * <p>Internal lock for concurrent access.</p>
	 */
	private ReadWriteLock readWriteLock;
	
	/**
	 * <p>Default constructor which builds an empty {@link Map}.</p> 
	 */
	public MultiValueMap()
	{	
		entries=new HashMap<Object,MultiValueMapEntry>();
		readWriteLock = new ReentrantReadWriteLock();
	}

	/**
	 * <p>
	 * Returns the {@link MultiValueMapEntry} with the given key i.&nbsp;e.&nbsp;the value to which the 
	 * specified key is mapped. This method yields the same result as calling 
	 * <code>({@link MultiValueMapEntry}){@link #get(Object)}</code>.
	 * </p>     
	 * <p>
	 * <b>Please note that although this method will return</b> <code>null</code> if the map 
	 * does not contain the specified key, it will also return <code>null</code> if a <code>null</code>
	 * value is mapped to the key. In essence a <code>null</code> return value does not indicate that the 
	 * map doesn't contain the specified key. Where this is required, the {@link #containsKey(Object)} 
	 * method should be used instead to determine if the key is present in the map.
	 * </p>
	 *
	 * @param key The key of the {@link MultiValueMapEntry} to be retrieved.
	 * @return The {@link MultiValueMapEntry} with the specified key i.e. the entry mapped to the 
	 * specified key.
	 * @see #get(java.lang.Object) 
	 * @see #containsKey(java.lang.Object)
	 * @see MultiValueMapEntry#key()  
	 */
	public MultiValueMapEntry getEntry(Object key)
	{
		lockForRead();
		try {
			return entries.get(key);
		} finally { unlockAfterRead(); }
	}
		
	/**
	 * <p>
	 * Returns all the values mapped to the specified key i.&nbsp;e.&nbsp;all the values of the 
	 * {@link MultiValueMapEntry} with the specified key. The same result can be obtained by invoking 
	 * {@link MultiValueMapEntry#readOnlyValues()} on the result of {@link #get(Object)}.
	 *</p>
	 *
	 * @param key The key whose associated values are to be retrieved.
	 * @return All the values mapped to the specified key. <b>Please note that If there are 
	 * no values mapped to this key, or the key is not contained in the map, 
	 * an empty list is returned</b>.
	 * 
	 * @see MultiValueMapEntry#readOnlyValues()  
	 */
	public List<Object> entryValues(Object key)
	{	
		List<Object> result;
		lockForRead();
		try 
		{
			MultiValueMapEntry entry = (MultiValueMapEntry)entries.get(key);		
			if (entry!=null)
				result=entry.readOnlyValues();
			else result=Collections.unmodifiableList(new ArrayList<Object>());
			
		} finally { unlockAfterRead(); }
		
		return result;
	}

	/**
	 * <p>
	 * Returns a string representation of the default value associated with the 
	 * specified key, where the default value is defined as: the value at index 0 in the 
	 * list of values mapped to this key. This is effectively the default value of the 
	 * {@link MultiValueMapEntry} with the specified key.
	 * </p>
	 *   
	 * @param key The key of the {@link MultiValueMapEntry} whose default value is to be returned.
	 * @return The string representation of the first element in the list of values 
	 * associated to the specified key, or <code>null</code> if there are no values mapped 
	 * to this key or if the value at the specified index is <code>null</code>.
	 * 
	 * @see #entryValue(java.lang.Object)
	 * @see MultiValueMapEntry#stringValue()
	 */	
	public String stringValue(Object key)
	{		
		String result;	
		lockForRead();
		try 
		{
			MultiValueMapEntry entry = getEntry(key);
			if (entry!=null)
				result=entry.stringValue();
			else result=null;				
		}finally { unlockAfterRead(); }
				
		return result;
	}	
	
	/**
	 * <p>
	 * Returns the {@link Long} representation of the default value associated with the 
	 * specified key, where the default value is defined as: the value at index 0 in the 
	 * list of values mapped to this key. This is effectively the default {@link Long} value 
	 * of the {@link MultiValueMapEntry} with the specified key.
	 * </p>
	 *   
	 * @param key The key of the {@link MultiValueMapEntry} whose default value is to be returned.
	 * @return The {@link Long} representation of the first element in the list of values 
	 * associated to the specified key, or <code>null</code> if there are no values mapped 
	 * to this key or if the value at the specified index cannot be converted to a {@link Long}.
	 * 
	 * @see #entryValue(java.lang.Object)
	 * @see MultiValueMapEntry#longValue()
	 */	
	public Long longValue(Object key)
	{
		Long result;
		lockForRead();
		try 
		{
			MultiValueMapEntry entry = getEntry(key);
			if (entry!=null)
				result=entry.longValue();
			else result=null;				
		}finally { unlockAfterRead(); }		
		return result;
	}	
	
	/**
	 * <p>
	 * Returns the {@link Integer} representation of the default value associated with the 
	 * specified key, where the default value is defined as: the value at index 0 in the 
	 * list of values mapped to this key. This is equivalent to  the default {@link Integer} 
	 *  value of the stored {@link MultiValueMapEntry} with the specified key.
	 * </p>
	 *   
	 * @param key The key of the {@link MultiValueMapEntry} whose default value is to be returned.
	 * @return The {@link Integer} representation of the first element in the list of values 
	 * associated to the specified key, or <code>null</code> if there are no values mapped 
	 * to this key or if the value at the specified index cannot be converted to an {@link Integer}.
	 * 
	 * @see #entryValue(java.lang.Object)
	 * @see MultiValueMapEntry#intValue()
	 */	
	public Integer intValue(Object key)
	{
		Integer result;	
		lockForRead();
		try 
		{
			MultiValueMapEntry entry = getEntry(key);
			if (entry!=null)
				result=entry.intValue();
			else result=null;				
		}finally { unlockAfterRead(); }
		return result;
	}	
	
	/**
	 * <p>
	 * Returns the {@link Double} representation of the default value associated with the 
	 * specified key, where the default value is defined as: the value at index 0 in the 
	 * list of values mapped to this key. This is the default {@link Double} value of the 
	 * stored {@link MultiValueMapEntry} with the specified key.
	 * </p>
	 *   
	 * @param key The key of the {@link MultiValueMapEntry} whose default {@link Double} 
	 * value is to be returned.
	 * @return The {@link Double} representation of the first element in the list of values 
	 * associated to the specified key, or <code>null</code> if there are no values mapped 
	 * to this key or if the value at the specified index cannot be converted to a {@link Double}.
	 * 
	 * @see #entryValue(java.lang.Object)
	 * @see MultiValueMapEntry#doubleValue()
	 */	
	public Double doubleValue(Object key)
	{
		Double result;	
		lockForRead();
		try 
		{
			MultiValueMapEntry entry = getEntry(key);
			if (entry!=null)
				result=entry.doubleValue();
			else result=null;				
		}finally { unlockAfterRead(); }
		return result;
	}	
	
	/**
	 * <p>
	 * Returns the {@link Boolean} representation of the default value associated with the 
	 * specified key, where the default value is defined as: the value at index 0 in the 
	 * list of values mapped to this key. This is the default boolean value of the 
	 * stored {@link MultiValueMapEntry} with the specified key.
	 * </p>
	 *   
	 * @param key The key of the {@link MultiValueMapEntry} whose default value is to be returned.
	 * @return The {@link Boolean} representation of the first element in the list of values 
	 * associated to the specified key, or <code>null</code> if there are no values mapped 
	 * to the key or if the value at the specified index cannot be converted to a {@link Boolean}.
	 * 
	 * @see #entryValue(java.lang.Object)
	 * @see MultiValueMapEntry#longValue()
	 */	
	public Boolean booleanValue(Object key)
	{
		Boolean result;	
		lockForRead();
		try 
		{
			MultiValueMapEntry entry = getEntry(key);
			if (entry!=null)
				result=entry.booleanValue();
			else result=null;				
		}finally { unlockAfterRead(); }
		return result;
	}	
			
	/**
	 * <p>
	 * Returns the default value associated with the specified key, where the default value is 
	 * defined as: the value at index 0 in the list of values mapped to this key. This is equivalent  
	 * to the default value of the {@link MultiValueMapEntry} with the specified key.
	 * </p>
	 *   
	 * @param key The key of the {@link MultiValueMapEntry} whose default value is to be returned.
	 * @return The first element in the list of values associated to the specified key, 
	 * or <code>null if there are no values mapped to this key.
	 * @see #entryValue(java.lang.Object)
	 * @see MultiValueMapEntry#value()
	 */	
	public Object entryValue(Object key)
	{
		Object result;
		lockForRead();
		try 
		{
			MultiValueMapEntry entry = getEntry(key);
			if (entry!=null)
				result=entry.value();
			else result=null;				
		}finally { unlockAfterRead(); }
		return result;
	}
	
	/**
	 * <p>
	 * Adds the specified {@link MultiValueMapEntry}  to this map, associating it with its key
	 * as returned by the call {@link MultiValueMapEntry#key()}. If the 
	 * map previously contained an association (i.e. an entry) for the key, the old association
	 * is removed and replaced with the new {@link MultiValueMapEntry}.
	 * </p>
	 * <p>
	 * Calling this method achieves the same result as calling 
	 * {@link #put(Object, MultiValueMapEntry)}, where the key (i.e. the first argument) is given by 
	 * invoking {@link MultiValueMapEntry#key()} on the entry being mapped (i.e. the second argument).
	 * </p>
	 *   
	 * @param entry The encapsulation of the entries to be added to the map.
	 * @see #addEntry(MultiValueMapEntry, boolean)
	 * @see #associateListToKey(Object, List) 
	 * @see #put(Object, MultiValueMapEntry)
	 */		
	public void addEntry(MultiValueMapEntry entry)
	{
		lockForWrite();
		try {
			entries.put(entry.key(),entry);
		} finally {
			unlockAfterWrite();
		}
	}
	
	/**
	 * <p>
	 * Convenience method which adds a {@link Collection collection} of {@link MultiValueMapEntry entries} to an instance. 
	 * Internally, this method simply iterates through the specified {@link Collection}, and 
	 * invokes {@link #addEntry(MultiValueMapEntry)} with each {@link MultiValueMapEntry element}.
	 * </p>
	 * 
	 * @param newEntries   The {@link Collection} of the {@link MultiValueMapEntry} instances to
	 * the map.
	 */
	public void addEntries(Collection<MultiValueMapEntry> newEntries)
	{
		lockForWrite();
		try {
			for (MultiValueMapEntry entry : newEntries)
				addEntry(entry);
		} finally {
			unlockAfterWrite();
		}
	}

	/**
	 * <p>
	 * Adds the specified {@link MultiValueMapEntry entry} to this instance, associating 
	 * it with its key as returned by the call {@link MultiValueMapEntry#key()}. Unlike 
	 * {@link #addEntry(MultiValueMapEntry)}, this method
	 * allows the user to dictate that: in the case where an {@link MultiValueMapEntry entry} with a matching key is 
	 * already mapped, the values taken from the parameter are  appended to 
	 * the list of associations for the key.
	 * </p> 
	 * 
	 * @param entry The encapsulation of values to be mapped to the key, as given by 
	 * {@link MultiValueMapEntry#key()}.
	 * @param replaceIfExists A value of <code>True</code> indicates that if there were previously values
	 * mapped to the key [as given by {@link MultiValueMapEntry#key()}], these should be overwritten
	 * by the values in the new entry. A value of <code>False</code> indicates that where previous 
	 * associations do exist, the new values should simply be appended to the list of values already associated 
	 * to the key.   
	 * 
	 * @see #addEntry(MultiValueMapEntry)
	 * @see #associateListToKey(Object, List)
	 * @see #put(Object, MultiValueMapEntry)
	 */		
	public void addEntry(MultiValueMapEntry entry, boolean replaceIfExists)
	{
		if (replaceIfExists)
			addEntry(entry);
		else
			associateListToKey(entry.key(),entry.readOnlyValues());		
	}

	/**
	 * <p>
	 * Associates the specified values to the given key. If the map contains previous associations
	 * to this key the new values are simply appended to the list of existing associations; else
	 * a new {@link  MultiValueMapEntry entry} is created with the specified key and values.
	 * </p>   
	 * 
	 * @param key The key to map the values to.
	 * @param values The values to map to the given key.
	 * 
	 * @see #associateValuesToKey(Object, Object...)
	 */
	public void associateListToKey(Object key, List<Object> values)
	{
		lockForWrite();
		try 
		{
			MultiValueMapEntry entry=(MultiValueMapEntry)entries.get(key);		
			//if the entry doesn't already exist, then add it to the map
			if (entry==null)
			{
				entry=new MultiValueMapEntry(key);
				entries.put(key,entry);
			}			
			entry.addAll(values);
		}
		finally { unlockAfterWrite(); }
	}

	/**
	 * <p>
	 * Associates the specified value(s) to the given key. If the map contains previous associations
	 * to this key the new value(s) are simply appended to the list of existing values; else
	 * a new {@link  MultiValueMapEntry entry} is created with the specified key, and the 
	 * new value(s) are associated to it.
	 * </p> 
	 * 
	 * @param key The key to map the given value(s) to.
	 * @param values One or more values to map to the given key.
	 * 
	 *  @see #associateListToKey(Object, List)
	 */		
	public void associateValuesToKey(Object key, Object... values)
	{associateListToKey(key,Arrays.asList(values));}

	/**
	 * <p>Removes all key-value(s) associations from an instance.</p>  
	 */
	@Override
	public void clear()
	{
		lockForWrite();
		try {
			unsynchronizedClear();
		} finally { unlockAfterWrite(); }
	}
	/**
	 * <p>
	 * This method provides the same functionality as the {@link #clear()} method, except 
	 * that it is not thread-safe. It is provided for implementations which desire greater 
	 * control over synchronization.
	 * </p>
	 */
	protected void unsynchronizedClear()
	{ entries.clear(); }

	/**
	 * <p>Returns the number of key-value(s) associations in the map.</p>
	 * 
	 * @return The number of key-value(s) mappings in this map.
	 */
	@Override
	public int size()
	{
		lockForRead();
		try {
			return entries.size();
		} finally { unlockAfterRead(); }
	}

	/**
	 * <p>Indicates if the map contains any mappings.</p>
	 *  
	 * @return <code>True</code> if the map does not contain any key-value(s) mappings.
	 * and <code>False</code> otherwise.
	 */
	@Override
	public boolean isEmpty()
	{
		lockForRead();
		try {
			return entries.isEmpty();
		} finally { unlockAfterRead(); }
	}

	/**
	 * <p>Tests if the specified key is present in the map.</p>
	 * 
	 * @param key The key for which the test is to be performed.
	 * @return <code>True</code> if the key is present in the map and 
	 * <code>False</code> otherwise.
	 */
	@Override
	public boolean containsKey(Object key)
	{
		lockForRead();
		try {
			return entries.containsKey(key);
		} finally { unlockAfterRead(); }						
	}

	/**
	 * <p>
	 * Returns true if the specified value exists in this map. It is 
	 * assumed that the supplied value is an instance of {@link  MultiValueMapEntry}.
	 * </p>
	 * @param entry The {@link  MultiValueMapEntry} instance whose presence this map 
	 * is to be tested for.
	 * @return <code>True</code> if the specified {@link  MultiValueMapEntry} entry 
         * is stored within the map and <code>False</code> otherwise.
	 */
	@Override
	public boolean containsValue(Object entry)
	{
		lockForRead();
		try {
			return entries.containsValue(entry);
		} finally { unlockAfterRead(); }						
	}

	/**
	 * <p>
	 * Copies all the {@link MultiValueMapEntry entries} from the specified argument to the instance. 
	 * Where this map and the specified map contain {@link MultiValueMapEntry entries} with the same key, 
	 * the values for these keys in the new map will overwrite what is currently stored in this map.
	 * </p>
	 * 
	 * @param map A {@link Map map} of {@link MultiValueMapEntry entries} to be copied 
	 * into the instance.
	 */
	@Override
	public void putAll(Map<? extends Object, ? extends MultiValueMapEntry> map)
	{	
		lockForWrite();
		try{
			entries.putAll(map);
		} finally {
			unlockAfterWrite();
		}
	}

	/**
	 * <p>
	 * Returns a read-only {@link Set set} which consists of all the mappings held in an instance. 
	 * Each element in the returned set is an instance of {@link MultiValueMapEntry}. 
	 * </p>
	 *
	 * @return A read-only view of the contents of an instance.
	 */
	public Set<Entry<Object,MultiValueMapEntry>> entrySet()
	{
		lockForRead();
		try {
			return  Collections.unmodifiableSet(entries.entrySet());
		} finally { unlockAfterRead(); }						
	}

	/**
	 * <p>
	 * Returns a {@link Set set}  view of the  {@link MultiValueMapEntry map-entry-keys} contained 
	 * in an instance. The {@link Set} is read-only and thus cannot be modified.
	 * </p>
	 * 
	 * @return The {@link Set set} of keys contained within the map
	 */
	public Set<Object> keySet()
	{
		lockForRead();
		try {
			return Collections.unmodifiableSet(entries.keySet());
		} finally { unlockAfterRead(); }										
	}

	/**
	 * <p>
	 * Returns the {@link  MultiValueMapEntry entry} with the specified key. A return value of 
	 * null does not necessarily indicate that the specified key is not stored in this map as 
	 * the key could be mapped to a null entry. 
	 * The {@link #containsKey(Object)} method should be used to 
	 * determine if a key is present in the map or not.
	 * </p>
	 * 
	 * @param key The key of the {@link MultiValueMapEntry entry} to be retrieved.
	 * @return The {@link MultiValueMapEntry entry} with the specified key. 
	 */
	public MultiValueMapEntry get(Object key)
	{
		lockForRead();
		try {
			return entries.get(key);
		} finally { unlockAfterRead(); }										
	}

	/**
	 * <p>Removes the {@link MultiValueMapEntry} associated with the specified key.</p>
	 * 
	 * @param key the key of the {@link MultiValueMapEntry} to be removed from the map.
	 */
	public MultiValueMapEntry remove(Object key)
	{
		lockForWrite();
		try {
			return entries.remove(key);
		} finally { unlockAfterWrite(); }										
	}

	/**
	 * <p>
	 * Associates the specified object with the given key in this map, where the 
	 * object is assumed to be an instance of {@link MultiValueMapEntry}. 
	 * <b>Please note that the specified key must be </b>the same as that returned
	 * by the method call <code>value.getKey()</code>.
	 * </p> 
	 * 
	 * <p>This method is provided for completeness purposes only, and users are 
	 * strongly encouraged to use the {@link #addEntry(MultiValueMapEntry)} or the  
	 * {@link #addEntry(MultiValueMapEntry, boolean)} methods instead.
	 * </p>
	 * 
	 * @param key The key to which the specified {@link MultiValueMapEntry}instance 
	 * 	should be mapped
	 * @param value The encapsulation of the {@link MultiValueMapEntry entries} to be 
	 * 	mapped to the specified key. This is assumed to be an instance of {@link MultiValueMapEntry}.
	 * @throws IllegalArgumentException If the specified value is not an instance of 
	 * {@link MultiValueMapEntry}, or if the specified key is not equal to the value 
	 * returned {@link MultiValueMapEntry#key()} when invoked on the supplied value.
	 * @see #addEntry(MultiValueMapEntry, boolean)
	 * @see #addEntry(MultiValueMapEntry)
	 */
	@Override
	public MultiValueMapEntry put(Object key, MultiValueMapEntry value)
	{		
		MultiValueMapEntry result;		
		lockForWrite();		
		try 
		{ 
			if (!(key.equals(value.key())))
				throw new IllegalArgumentException("The specified key '"+key+
													"' is not equal to the key '"+
													value.key() + "' of the supplied entry");
			
			//if this key already has a value in the map, then return it
			if (get(key)!=null) result=entries.get(key);
			else result = null;
						
			//now add the value
			addEntry(value,true);
		}
		finally {
			unlockAfterWrite();
		}
		return result;
	}

	/**
	 * <p>
	 * Returns a read-only {@link Collection collection} view of the {@link MultiValueMapEntry entries}
	 *  stored in this map. Note that this {@link Collection collection} contains elements of the type</b> 
	 * {@link MultiValueMapEntry}.
	 * </p>
	 *
	 * @return     A {@link Collection collection} view of the {@link MultiValueMapEntry entries} 
	 * contained in this map.
	 * @see          java.util.Map#values()
	 */
	@Override
	public Collection<MultiValueMapEntry> values()
	{
		lockForRead();
		try {
			return Collections.unmodifiableCollection(entries.values());
		} finally { unlockAfterRead(); }
	}
	
	@Override
	public int hashCode()
	{
		lockForRead();
		try 
		{					
			Collection<MultiValueMapEntry> values = values();
			int result=
				SimpleHashCodeCalculator.calculateFromFields(values);
			return result;
			
		}  finally { unlockAfterRead(); }												
	}
		
	@Override
	public boolean equals(Object candidate)
	{
		boolean result = true;
		lockForRead();
		try 
		{				
			if (!(candidate instanceof MultiValueMap))
				result=false;
			
			if (result) 
			{				
				MultiValueMap candidateMap = (MultiValueMap)candidate;
				if (this.size() != candidateMap.size())
					result = false;
				else 
				{
					Collection<MultiValueMapEntry> entryValues = values();
					
					for (MultiValueMapEntry entryValue : entryValues) {
						if (!candidateMap.containsValue(entryValue)) {
							result = false;
							break;
						} //end if (!cand..
					} //end for
					
				}
			}
			
		} finally { unlockAfterRead(); }
		
		return result;
	}
	
	
	/**
	 * <p>
	 * Returns a reference to the internal lock used for concurrent 
	 * access.
	 * </p>
	 * @return		A reference to the internal concurrency lock.
	 */
	private ReadWriteLock getReadWriteLock() { return readWriteLock; }

	/**
	 * <p>Locks the instance for reading.</p>	
	 */
	protected void lockForRead() {getReadWriteLock().readLock().lock();}
	
	/**
	 * <p>Releases the read-lock on the instance.</p> 
	 */
	protected void unlockAfterRead() {getReadWriteLock().readLock().unlock();}

	/**
	 * <p>Locks the instance for writing.</p>	
	 */	
	protected void lockForWrite() {getReadWriteLock().writeLock().lock();}

	/**
	 * <p>Releases the write-lock on the instance.</p> 
	 */	
	protected void unlockAfterWrite() {getReadWriteLock().writeLock().unlock();}
}//end class def
